package ciel.scene;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import org.mt4j.AbstractMTApplication;
import org.mt4j.components.MTComponent;
import org.mt4j.components.TransformSpace;
import org.mt4j.components.visibleComponents.shapes.MTRectangle.PositionAnchor;
import org.mt4j.components.visibleComponents.widgets.MTBackgroundImage;
import org.mt4j.components.visibleComponents.widgets.MTImage;
import org.mt4j.components.visibleComponents.widgets.buttons.MTSvgButton;
import org.mt4j.input.inputProcessors.IGestureEventListener;
import org.mt4j.input.inputProcessors.MTGestureEvent;
import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapEvent;
import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor;
import org.mt4j.input.inputProcessors.globalProcessors.CursorTracer;
import org.mt4j.sceneManagement.AbstractScene;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;

import processing.core.PImage;
import ciel.annotator.CielAnnotator;
import ciel.debugging.CielDebugger;
import ciel.helper.CielHelper;
import ciel.image.CielImage;
import ciel.image.CielThumbnail;
import ciel.menu.CielGlobalMenu;
import ciel.menu.CielThumbnailMenu;
import ciel.res.CielSettings;
import ciel.utils.CielHtmlCreator;
import ciel.utils.CielMath;

/**
 * Diese Scene stellt die zentrale Interaktionskomponente in Ciel dar. Sie stellt die Thumbnail-Menüs, die geöffneten Bilder sowie
 * die Annotationswerkzeuge dar. 
 * 
 * @author Alexander Bazo
 * @version 1.0
 *
 */

//TODO Add ImageList to replace ThumbnailMenus

public class CielImageScene extends AbstractScene {
	//private ArrayList<MTImage> images_for_menu;
	
	/**
	 * Alle Bilder im überwachten Ordner
	 */
	private HashMap<String, CielImage> images;
	/**
	 * Jedes Thumbnail-Menü hält seperat Kopien der überwachten Bilder, so dass eine unabhänige Manipulation der
	 * Bilder in den verschiedenen Menüs möglich ist. Um Speicherplatz zu sparen werden die Bilder aus Dateien generiert, 
	 * die beim Import aus den, hinsichtlich ihrer Dimenion reduzierten, Orginaldateien gewonnen werden. Diese Thumbnail-Dateien 
	 * existieren nur währen der Laufzeit und einmalig pro Orginaldatei.
	 */
	private HashMap<String, CielThumbnail> thumbs_bottom_left;
	private HashMap<String, CielThumbnail> thumbs_bottom_right;
	private HashMap<String, CielThumbnail> thumbs_top_left;
	private HashMap<String, CielThumbnail> thumbs_top_right;
	
	/**
	 * working ist wahr, wenn die HashMap mit den Bilder momentan bearbeitet wird. Die Flag ist notwendig, um unkompliert 
	 * Zugriffsprobleme beim gleichzeitigen Ansprechen eines Bildes durch zwei nebenläufige Methoden zu verhindern.
	 */
	public static boolean working = false;
	
	/**
	 * Der observer-Thread überwacht zur Laufzeit Änderungen im Bilderordner und veranlasst beim Hinzufügen eines neuen Bildes
	 * dessen Import.
	 */
	private Thread observer;
	
	/**
	 * Konstruktor der CielImagescene. Nach dem Aufruf des Elternkonstruktors werden die verschiedenen Komponenten der Scene in
	 * seperaten Methoden initialisiert. Eine genauere Beschreibung findet sich in den jeweiligen Methoden. Die Reihenfolge der 
	 * Initialisierung beruht auf Abhängigkeiten.
	 * 
	 * @param mtApplication mt4j-Applikation, die die Scene darstellt
	 * @param name Name der Scene
	 * @param img Hintergrundbild.  Wenn die Scenen in einem nebenläufigen Thread und nicht im Render-Thread ausgeführt wird muss
	 * das Hintergrundbild im Render-Thread erzeugt werden und nachträglich hinzugefügt werden. @see AbstractMTApplication (loadImage)
	 */
	public CielImageScene(AbstractMTApplication mtApplication, String name, MTBackgroundImage img) {
		super(mtApplication, name);
		/**
		 * Regelt die Reihenfolge in der in der Komponenten auf dem Bildschirm gezeichnet werden. Wenn der DepthBuffer deaktiviert ist
		 * werden neue Komponenten immer über der zuletzt gezeichneten Komponente gezeichnet. Die Deaktivierung führt bei reinen
		 * 2d-Anwendungen zu leichtem Performance-Gewinn.  
		 */
		getCanvas().setDepthBufferDisabled(true);
		initDimensions();
		initComponents();
		initImageArray();
		initUI(img);	
		initGlobalMenu();
		initGestures();
		initFileObserver();
	}
	
	/**
	 * Setzt verschiedene Größenangaben für die Darstellung von UI-Elementen in Abhängigkeit von der Bildschirmauflösung.
	 */
	private void initDimensions() {
		CielSettings.Dimensions.thumbnailWidth = CielSettings.Dimensions.SCREEN_WIDTH/60;
	}
	
	/**
	 * Initialisiert die verschiedenen Members der Scene.
	 */
	private void initComponents() {
		images = new HashMap<String, CielImage>();
		thumbs_bottom_left = new HashMap<String, CielThumbnail>();
		thumbs_bottom_right = new HashMap<String, CielThumbnail>();
		thumbs_top_left = new HashMap<String, CielThumbnail>();
		thumbs_top_right = new HashMap<String, CielThumbnail>();
	}
	
	/**
	 * Initialisert die Gestenerkennung für diese Scene. Alle vorhandenen InputProcessors und GestureEventListeners werden 7
	 * entfernt und nur die notwendigen für die Scene hinzugefügt. Da die Manipulation der Kinder-Komponenten über deren eigene
	 * Processors and EventListeners realisiert wird, wird hier nur ein TapProcessor gebraucht um das GlobalMenu dieser Scene zu öffnen.
	 */
	private void initGestures() {
		/**
		 * Entfernen aller InputProcessors und EventListeners von dieser Scene
		 */
		getCanvas().unregisterAllInputProcessors(); 
		getCanvas().removeAllGestureEventListeners(); 
		
		/**
		 * Erstellen und Hinzufügen eines neuen TapProcessors
		 */
		this.registerGlobalInputProcessor(new CursorTracer(getMTApplication(), this));
		TapProcessor tp = new TapProcessor(getMTApplication());
		/**
		 * Setzt die maximale Distanz zwischen Start und Ende eine Tapgeste. Start ist die Position, auf der der Benutzer den Finger
		 * auf dem Bildschirm platziert, Ende ist die Position auf der er den Finger wieder vom Bildschirm nimmt. Ist die Distanz zu groß
		 * wird die Geste nicht als solche erkannt. Damit ist ein Abbruch der Tap- bzw. Wischgeste möglich in dem der Finger während der 
		 * Geste aus diesem Bereich entfernt wird.
		 */
		tp.setMaxFingerUpDist(100);
		getCanvas().registerInputProcessor(tp);
		
		/**
		 * Hinzufügen eines GestureListener für das Aktivieren des GlobalMenu
		 */
		getCanvas().addGestureListener(TapProcessor.class, new IGestureEventListener() {
			@Override
			public boolean processGestureEvent(MTGestureEvent ge) {
				TapEvent te = (TapEvent)ge;
				/**
				 * Wenn die Geste beendet wird werden die aktuell im Cursor gespeicherten Positionen für die Darstellung des GlobalMenu
				 * benutzt.
				 */
				if(te.isTapCanceled()) {
					((CielGlobalMenu) getCanvas().getChildByName(CielSettings.Childs.GLOBALMENU)).show(te.getCursor());
				}
				return false;
			}
		});
		
		
	}

	/**
	 * Erzeugen und Hinzufügen des GlobalMenus zu dieser Scene. Das Menü ist standardmäßig invisible und wird erst durch eine 
	 * entsprechende Geste an der durch diese Geste bestimmten Position sichtbar gemacht.
	 */
	private void initGlobalMenu() {
		CielGlobalMenu globalMenu = new CielGlobalMenu(getMTApplication(), 0, 0, 0, 0);
		globalMenu.setScene(this);
		globalMenu.setName(CielSettings.Childs.GLOBALMENU);
		globalMenu.setVisible(false);
        getCanvas().addChild(globalMenu);
	}
	
	/**
	 * Lädt alle Bilder aus dem überwachten Ordner. Der Ordner wird in CielSettings bestimmt (IMAGEDIR)
	 * Berücksichtigt werden alle jpeg Bilder, die anhand ihrer Dateiendung bestimmte werden.
	 */
	private void initImageArray() {
		
		File storage = new File(CielSettings.Filesystem.IMAGEDIR);
		File[] photoArray = storage.listFiles();
		
		for(File f: photoArray) {
			if(f.getName().contains(".jpg") || f.getName().contains(".jpeg") || f.getName().contains(".JPG") || f.getName().contains("JPEG")) {
				importFile(f, CielSettings.Scans.INITIAL_SCAN);
			}
		}
		storage = null;
		photoArray = null;
	}
	
	/**
	 * Setzt das Hintergrundbild der Scene und initialisiert die UI-Komponenten. Dazu werden die Buttons zum öffnen der
	 * Thumbnailmenüs erzeugt und auch diese Menüs erzeugt und zu Scene hinuzgefügt. Anschließend werden die Thumbnails zu den 
	 * Menüs hinzugefügt. Abschließend werden die Buttons und Menüs positioniert. Die Operationen für die vier Menüs werden 
	 * nebenläufig in vier Threads durchgeführt.
	 * 
	 * @param img - Hintergrundbild für diese Scene
	 */
	private void initUI(MTBackgroundImage img) {
		
		loadBackgroundImage(img);
		
		new Thread(new Runnable() {
			public void run() {
				initUserButton(CielSettings.Positions.BOTTOM_LEFT);
	        	CielThumbnailMenu menu_bottom_left = new CielThumbnailMenu(getMTApplication(), new Vector3D(0,CielSettings.Dimensions.SCREEN_HEIGHT), 100, 100);
	            menu_bottom_left.setUserData("scene", this);
	            menu_bottom_left.setName(CielSettings.Childs.THUMBNAILMENU_BOTTOM_LEFT);
	            getCanvas().addChild(menu_bottom_left);
	            menu_bottom_left.center = new Vector3D(menu_bottom_left.getWidthXY(TransformSpace.GLOBAL)/2,CielSettings.Dimensions.SCREEN_HEIGHT - menu_bottom_left.getHeightXY(TransformSpace.GLOBAL)/2);
	            menu_bottom_left.addThumbs(thumbs_bottom_left);
	            menu_bottom_left.position = CielSettings.Positions.BOTTOM_LEFT;
	        }
	    }).start();
		
		new Thread(new Runnable() {
			public void run() {
				initUserButton(CielSettings.Positions.TOP_LEFT);
				CielThumbnailMenu menu_top_left = new CielThumbnailMenu(getMTApplication(), new Vector3D(0,CielSettings.Dimensions.SCREEN_HEIGHT), 100, 100);
				menu_top_left.setUserData("scene", this);
				menu_top_left.setName(CielSettings.Childs.THUMBNAILMENU_TOP_LEFT);
				getCanvas().addChild(menu_top_left);
				menu_top_left.center = new Vector3D(menu_top_left.getWidthXY(TransformSpace.GLOBAL)/2, menu_top_left.getHeightXY(TransformSpace.GLOBAL)/2);
				menu_top_left.addThumbs(thumbs_top_left);
				menu_top_left.setPositionGlobal(new Vector3D(0,0));
				menu_top_left.rotateZGlobal(menu_top_left.getCenterPointGlobal(), 90);
				menu_top_left.position = CielSettings.Positions.TOP_LEFT;
			}
		}).start();
		
		new Thread(new Runnable() {
			public void run() {
				initUserButton(CielSettings.Positions.TOP_RIGHT);
				CielThumbnailMenu menu_top_right = new CielThumbnailMenu(getMTApplication(), new Vector3D(0,CielSettings.Dimensions.SCREEN_HEIGHT), 100, 100);
				menu_top_right.setUserData("scene", this);
				menu_top_right.setName(CielSettings.Childs.THUMBNAILMENU_TOP_RIGHT);
				getCanvas().addChild(menu_top_right);
				menu_top_right.center = new Vector3D(CielSettings.Dimensions.SCREEN_WIDTH - menu_top_right.getWidthXY(TransformSpace.GLOBAL)/2, menu_top_right.getHeightXY(TransformSpace.GLOBAL)/2);
				menu_top_right.addThumbs(thumbs_top_right);
				menu_top_right.setPositionGlobal(new Vector3D(CielSettings.Dimensions.SCREEN_WIDTH,0));
				menu_top_right.rotateZGlobal(menu_top_right.getCenterPointGlobal(), 180);
				menu_top_right.position = CielSettings.Positions.TOP_RIGHT;
			}
		}).start();
		
		new Thread(new Runnable() {
			public void run() {
				initUserButton(CielSettings.Positions.BOTTOM_RIGHT);
				CielThumbnailMenu menu_bottom_right = new CielThumbnailMenu(getMTApplication(), new Vector3D(0,CielSettings.Dimensions.SCREEN_HEIGHT), 100, 100);
				menu_bottom_right.setUserData("scene", this);
				menu_bottom_right.setName(CielSettings.Childs.THUMBNAILMENU_BOTTOM_RIGHT);
				getCanvas().addChild(menu_bottom_right);
				menu_bottom_right.center = new Vector3D(CielSettings.Dimensions.SCREEN_WIDTH - menu_bottom_right.getWidthXY(TransformSpace.GLOBAL)/2, CielSettings.Dimensions.SCREEN_HEIGHT - menu_bottom_right.getHeightXY(TransformSpace.GLOBAL)/2);
				menu_bottom_right.addThumbs(thumbs_bottom_right);
				menu_bottom_right.setPositionGlobal(new Vector3D(CielSettings.Dimensions.SCREEN_WIDTH,CielSettings.Dimensions.SCREEN_HEIGHT));
				menu_bottom_right.rotateZGlobal(menu_bottom_right.getCenterPointGlobal(), 270);
				menu_bottom_right.position = CielSettings.Positions.BOTTOM_RIGHT;
			}
		}).start();
	}
	
	/**
	 * Erzeugt einen UserButton zum öffnen eines Thumbnailmenüs an einer bestimmten Position. Über den Positionsparemeter wird
	 * definiert welches Menü zu öffnen ist.
	 * 
	 * @param Position des UserButtons auf dem Bildschirm. Die Positionen sind in @see CielSettings definiert.
	 */
	private void initUserButton(int Position) {
		MTSvgButton userButtonOpen = new MTSvgButton(getMTApplication(), "src/ciel/res/button_addImage_svg.svg");
		float buttonScale = CielMath.getScaleDimension(CielSettings.Scale.userButton, userButtonOpen.getHeightXYGlobal());
		userButtonOpen.scale(buttonScale, buttonScale, buttonScale, new Vector3D(0,0));
		
		float buttonHeight = userButtonOpen.getHeightXYRelativeToParent();
		float buttonWidth = userButtonOpen.getWidthXYRelativeToParent();
		CielSettings.Dimensions.userButtonRadius = buttonHeight;
		
		
		userButtonOpen.addGestureListener(TapProcessor.class, new IGestureEventListener() {
			public boolean processGestureEvent(MTGestureEvent ge) {	
				TapEvent te = (TapEvent)ge;
				if(te.isTapDown()) {
					showThumbnailMenu(te.getTarget().getName());
				}
				return false;
			}
		});
		
		switch(Position) {
		case CielSettings.Positions.TOP_LEFT:
			userButtonOpen.rotateZ(new Vector3D(0,0), 135);
			userButtonOpen.setPositionGlobal(new Vector3D(buttonWidth/2,buttonHeight/2));
			userButtonOpen.setName(CielSettings.Childs.USERMENU_TOP_LEFT);
			break;
			
		case CielSettings.Positions.TOP_RIGHT:
			userButtonOpen.rotateZ(new Vector3D(0,0), 215);
			userButtonOpen.setPositionGlobal(new Vector3D(CielSettings.Dimensions.SCREEN_WIDTH-buttonWidth/2,buttonHeight/2));
			userButtonOpen.setName(CielSettings.Childs.USERMENU_TOP_RIGHT);
			break;

		case CielSettings.Positions.BOTTOM_RIGHT:
			userButtonOpen.rotateZ(new Vector3D(0,0), 315);
			userButtonOpen.setPositionGlobal(new Vector3D(CielSettings.Dimensions.SCREEN_WIDTH-buttonWidth/2,CielSettings.Dimensions.SCREEN_HEIGHT-buttonHeight/2));
			userButtonOpen.setName(CielSettings.Childs.USERMENU_BOTTOM_RIGHT);
			break;
			
		case CielSettings.Positions.BOTTOM_LEFT:
			userButtonOpen.rotateZ(new Vector3D(0,0), 45);
			userButtonOpen.setPositionGlobal(new Vector3D(buttonWidth/2,CielSettings.Dimensions.SCREEN_HEIGHT-buttonHeight/2));
			userButtonOpen.setName(CielSettings.Childs.USERMENU_BOTTOM_LEFT);
			break;
			
		default:
			break;
		}
		
		this.getCanvas().addChild(userButtonOpen);
	}

	/**
	 * Erzeugt und starten eine FileObserver Thread der ein bestimmtes Verzeichnis überwacht und bei hinzufügen von
	 * neuen JPG-Dateien in diesen importFile(File f) aufruft.
	 */
	private synchronized void initFileObserver() {
		/**
		 * Der zu überwachenden Ordner ist zentral in @see CielSettings definiert
		 */
		observer = new FileObserver(CielSettings.Filesystem.IMAGEDIR);
		observer.start();
	}
	
	private void loadBackgroundImage(MTBackgroundImage img) {
		CielDebugger.log("Loading BackgroundImage");
		getCanvas().addChild(img);
	}
	
	/**
	 * Lädt ein Bild aus eine bestimmten Datei in das Programm
	 *
	 * @param f Zu importierende Bild-Datei
	 * @param scan Flag die zwischen einem Import bei Programmstart und zur Laufzeit disambiguiert
	 */
	private void importFile(File f, int scan) {
		/**
		 * Zufallsgenerator dient der Positionierung des Thumbnails innerhalb der verschiedenen Thumbnailmenüs.
		 */
		Random rand = new Random();
		
		CielDebugger.log("Loading and Creating image: "+f.getName()+" ("+f.length()/1024+"KB) from "+f.getPath());
		
		/**
		 * Kreiert ein Thumbnail (kleinere Version des Orginals) aus der übergebenen Datei und speichert es in 
		 * einem entsprechenden Unterordner (definiert in @see CielSettings), so dass im Weiteren darauf zugegriffen
		 * werden kann.
		 */
		createThumbnail(f);
		CielDebugger.log("Thumbnail Created");
		/**
		 * Erstellt PImages aus der übergebenen Datei und dem erstellten Thumbnail
		 */
		PImage tmpPImage = getMTApplication().loadImage(f.getPath());
		//images_for_menu.add(new MTImage(getMTApplication(), tmpPImage));
		PImage tmpThumbnail = getMTApplication().loadImage(CielSettings.Filesystem.THUMBDIR+"//"+f.getName());
		
		/**
		 * Für jedes Thumbnailmenü wir ein CielThumbnail erzeugt
		 * Dem Thumbnail werden sowohl die Scene als auch der Dateiname des Orginals als UserData übergeben, so dass später eine Idenitifkation
		 * möglich ist.
		 */
		CielThumbnail tmpThumbnail_1 = new CielThumbnail(getMTApplication(), tmpThumbnail);
		tmpThumbnail_1.setUserData("scene", this);
		tmpThumbnail_1.setUserData("name", f.getName());
		
		CielThumbnail tmpThumbnail_2 = new CielThumbnail(getMTApplication(), tmpThumbnail);
		tmpThumbnail_2.setUserData("scene", this);
		tmpThumbnail_2.setUserData("name", f.getName());
		
		CielThumbnail tmpThumbnail_3 = new CielThumbnail(getMTApplication(), tmpThumbnail);
		tmpThumbnail_3.setUserData("scene", this);
		tmpThumbnail_3.setUserData("name", f.getName());
		
		CielThumbnail tmpThumbnail_4 = new CielThumbnail(getMTApplication(), tmpThumbnail);
		tmpThumbnail_4.setUserData("scene", this);
		tmpThumbnail_4.setUserData("name", f.getName());
		
		/**
		 * Die per createThumbnail() erzeugte Datei wird nicht mehr benötigt und wird gelöscht.
		 */
		File tmpNailFile = new File(CielSettings.Filesystem.THUMBDIR + AbstractMTApplication.separatorChar + f.getName());
		tmpNailFile.delete();
		
		/**
		 * Erzeugt das eigentlich Bild, das später vom Benutzer manipuliert werden kann. Es wird in der Mitte des Bildschirm, mit leicht 
		 * zufällig abweichenden Koordinaten positioniert. 
		 */
		CielImage tmpImage = new CielImage(getMTApplication(), tmpPImage);
		tmpImage.scaleGlobal(0.1f, 0.1f, 0.1f, new Vector3D(0,0));
		//TODO Remove positioning because Images are now dragged into the scene an do not become visible in the scene center but 
		float x = CielSettings.Dimensions.SCREEN_WIDTH/2 + (-100 + rand.nextInt(200));
		float y = CielSettings.Dimensions.SCREEN_HEIGHT/2 + (-100 + rand.nextInt(200));
		tmpImage.setPositionGlobal(new Vector3D(x,y));
		/**
		 * Dem Bild werden sowohl die Scene als auch der Dateiname des Orginals als UserData übergeben, so dass später eine Idenitifkation
		 * möglich ist.
		 */
		tmpImage.setUserData("scene", this);
		tmpImage.setUserData("name", f.getName());
		
		/**
		 * Fügt dem neuen CielImage UI-Elemente zum schließen des Bildes und zum Starten der Annotation hinzu. Dies geschieht nach der 
		 * Skalierung des Bildes um die Elemente relativ zur Größe des Bildes zu skalieren.
		 */
		tmpImage.addButtons(getMTApplication());
		
		/**
		 * Das erstellte Bild wird in die verschiedenen HashMaps der Scene eingetragen. Als Key wird der Dateiname benutzt, 
		 * der eine eindeutige Disambiguation erlaubt.
		 */
		images.put(f.getName(), tmpImage);
		thumbs_bottom_left.put(f.getName(), tmpThumbnail_1);
		thumbs_top_left.put(f.getName(), tmpThumbnail_2);
		thumbs_top_right.put(f.getName(), tmpThumbnail_3);
		thumbs_bottom_right.put(f.getName(), tmpThumbnail_4);
		
		/**
		 * importFile(File f) kann aus zwei verschiedenen Zuständen aufgerufen werden
		 * 1) Beim Prorgammstart, wenn alle bereits vorhandenen Bilder eingelesen werden
		 * 2) Zur Laufzeit, wenn ein neues Bild in den überwachten Ordner eingefügt wurde
		 * 
		 * Im ersten Fall (INITIAL_SCAN) werden die Thumbnails in initUI() erzeugt und den Menüs als Kinder hinzugefügt 
		 * Im zweiten Fall (RUNTIME_SCAN) werden die Thumnnails direkt nach ihrer Erzeugung den Menüs als Kinder hinzugefügt
		 */
		if(scan == CielSettings.Scans.RUNTIME_SCAN) {
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_BOTTOM_LEFT)).addThumb(tmpThumbnail_1);
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_TOP_LEFT)).addThumb(tmpThumbnail_2);
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_TOP_RIGHT)).addThumb(tmpThumbnail_3);
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_BOTTOM_RIGHT)).addThumb(tmpThumbnail_4);
		}
		
		/**
		 * Die tempörären Bilddateien die nicht mehr gebraucht werden werden freigebenen; anschließend wird der Runtime ein aufruf des
		 * GarbageCollectors vorgeschlagen.
		 */
		tmpPImage = null;
		tmpThumbnail = null;
		tmpImage = null;
		tmpThumbnail_1 = null;
		tmpThumbnail_2 = null;
		tmpThumbnail_3 = null;
		tmpThumbnail_4 = null;
		System.gc();
		
		CielDebugger.log("ImageProcessing complete");
	}
	
	/**
	 * Fügt ein Bild aus der HashMap dem Canvas dieser Scene hinzu. Das Bild wird mit einer Animation auf dem Canvas geöffnet und 
	 * die Thumbnails für diese Bild werden in allen Menüs deaktiviert.
	 * @param filename DateiName des Bildes anhand dessen in der HashMap gesucht wird.
	 * @param position Gewünschte Position des Bildes auf dem Canvas
	 */
	public void addImageToCanvas(String filename, Vector3D position) {
		
		getCanvas().addChild(images.get(filename));
		if(position != null) {
			images.get(filename).setPositionGlobal(position);
		}
		images.get(filename).openWithAnimation();
		thumbs_bottom_left.get(filename).changeStatus();
		thumbs_bottom_right.get(filename).changeStatus();
		thumbs_top_left.get(filename).changeStatus();
		thumbs_top_right.get(filename).changeStatus();
	}
	
	/**
	 * Fügt ein Annotationsmodul für ein, anhand des Parameter bestimmten, Bildes auf dem Canvas hinzu.
	 * @param filename DateiName des Bildes für das ein Annotationsmodul hinzugefügt werden soll.
	 */
	public void addAnnotator(final String filename) {
		
		Vertex vertex = new Vertex(images.get(filename).getPosition(TransformSpace.GLOBAL));
		images.get(filename).setVisible(false);
		float w = (float) (images.get(filename).getWidthXY(TransformSpace.GLOBAL) * 0.8);
		float h = (float) (images.get(filename).getHeightXY(TransformSpace.GLOBAL) * 0.8);
		if(h > w) {
			float x = w;
			w = h;
			h = x;
		}
		h = h/2;
		CielAnnotator annotator = new CielAnnotator(getMTApplication(), vertex, w, h, filename, this);
		annotator.setPositionGlobal(images.get(filename).getPosition(TransformSpace.GLOBAL));
		annotator.setUserData("scene", this);
		annotator.setUserData("name", filename+"Annotator");
		annotator.setName(filename+"Annotator");
		getCanvas().addChild(annotator);
		//must be called outside annotators constructor
		annotator.initModuls();
		/**
		 * Rotiert das Annotationsmodul so, dass es mit der momentanen Rotation des Bildes übereinstimmt.
		 */
		annotator.rotateZ(annotator.getCenterPointGlobal(), images.get(filename).currentRotation);
	}
	
	/**
	 * Setzt den Annotationsstring für ein bestimmtes Bild, definiert durch den DateiNamen
	 * @param filename DateiName des Bildes für das ein Annotationsmodul hinzugefügt werden soll.
	 * @param annotation Annotationsstring
	 */
	public void setAnnotation(String filename, String annotation) {
		CielDebugger.log("Setting annotation: \"" + annotation + "\" for: " + filename);
		images.get(filename).setNote(annotation);
	}
	
	/**
	 * Gibt den Annotationsstring für ein bestimmtes Bild, definiert durch den DateiNamen, zurück
	 * @param filename DateiName des Bildes
	 * @return Annotaionsstring des Bildes
	 */
	public String getAnnotation(String filename) {
		CielDebugger.log("Getting annotation for: "+filename);
		return (images.get(filename)).getNote();
	}
	
	/**
	 * Entfernt das Annotationsmodul eines bestimmten Bildes, gibt die Ressourcen frei und schlägt dem System den Aufruf des 
	 * GarbageCollectors vor. Anschließend wird das entsprechende Bild wieder sichtbar gemacht.
	 * @param filename DateiName des Bildes, dessen Annotationsmodul entfernt werden soll.
	 * @param annotator Name des Annotaionsmodul das entfernt werden soll
	 */
	public void removeAnnotator(String filename, String annotator) {
		CielDebugger.log("Rmoving Annotator: "+annotator+" for image: "+filename);
		MTComponent c = getCanvas().getChildByName(annotator);
		getCanvas().removeChild(c);
		c.destroy();
		System.gc();
		images.get(filename).setVisible(true);
	}
	
	/**
	 * Zeigt einen CileHelper, bestehend aus mehreren, durchschaltbare Screenshots mit Hinweisen, an.
	 * @param position Position auf dem Bildschirm an der der Helper erscheinen soll
	 */
	public void addHelper(Vector3D position) {
		/**
		 * Es können beliebig viele CielHelper durch die Benutzer angezeigt werden. Um die einzelnen Dialog einwandfrei zu identifizieren 
		 * wird ein Timestamp als Name der Komponente genutzt. 
		 */
		String name = String.valueOf(System.currentTimeMillis());
		CielHelper helper = new CielHelper(getMTApplication(), CielSettings.Dimensions.SCREEN_WIDTH/8, CielSettings.Dimensions.SCREEN_HEIGHT/8);
		helper.setName(name);
		helper.setUserData("scene", this);

		/**
		 * Hinzufügen der verschiedenen Bilder zum CielHelper
		 */
		helper.addHelpImage(new MTImage(getMTApplication(), getMTApplication().loadImage("src/ciel/helper/0_download.png")));
		helper.addHelpImage(new MTImage(getMTApplication(), getMTApplication().loadImage("src/ciel/helper/1_thumbnailmenu.png")));
		helper.addHelpImage(new MTImage(getMTApplication(), getMTApplication().loadImage("src/ciel/helper/2_globalmenu.png")));
		helper.addHelpImage(new MTImage(getMTApplication(), getMTApplication().loadImage("src/ciel/helper/3_dragToCanvas.png")));
		helper.addHelpImage(new MTImage(getMTApplication(), getMTApplication().loadImage("src/ciel/helper/4_imageControll.png")));
		helper.addHelpImage(new MTImage(getMTApplication(), getMTApplication().loadImage("src/ciel/helper/5_annotation.png")));
		
		/**
		 * Positionsanker wird auf das Zentrum des Helpers gesetzt und anschließend wird die Komponente platziert und der Scene hinzugefügt.
		 */
		helper.setAnchor(PositionAnchor.CENTER);
		helper.setPositionGlobal(position);
		getCanvas().addChild(helper);
	}
	
	/**
	 * Entfernt ein bestimmtes Bild von der Scene
	 * @param filename DateiName des Bildes, das entfernt werden soll
	 */
	public void removeImageFromCanvas(String filename) {
		if(getCanvas().containsChild(images.get(filename))){
			CielDebugger.log("Removing Image "+filename+" from canvas");
			getCanvas().removeChild(images.get(filename));
			thumbs_bottom_left.get(filename).changeStatus();
			thumbs_bottom_right.get(filename).changeStatus();
			thumbs_top_left.get(filename).changeStatus();
			thumbs_top_right.get(filename).changeStatus();
		}
	}
	
	/**
	 * Entfernt alle momentan dargestellten Bilder von der Scene
	 */
	public void removeAllImagesFromCanvas() {
		CielDebugger.log("removeAllImagesFromCanvas");
		/**
		 * Während die Bilder entfernt werden wird die working-Flag gesetzt um zu verhindern, dass andere Methoden auf die HashMap
		 * zugreifen können.
		 */
		working = true;
		for(Entry<String, CielImage> image: images.entrySet()) {
			removeImageFromCanvas(image.getKey());
		}
		working = false;
	}

	/**
	 * Startet den Export aller Bilder und Annotationen in eine HTML-Struktur
	 */
	public void exportHTML() {
		CielHtmlCreator.exportHTML(images);
	}
	
	/**
	 * Stellt ein ThumbnailMenu auf dem Bildschirm dar. Anhand des Button, der die Methode ausgelöst hat wird entschieden, 
	 * welches Menü anzueigen ist.
	 * @param Button Name des Buttons, von dem die Methode aufgerufen wird
	 */
	private void showThumbnailMenu(String Button) {
		if(Button.equals(CielSettings.Childs.USERMENU_BOTTOM_LEFT)) {
			CielDebugger.log("Showing Thumbnailmenu @ BOTTOM_LEFT");
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_BOTTOM_LEFT)).setVisible(true);
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_BOTTOM_LEFT)).sendToFront();
		} else if(Button.equals(CielSettings.Childs.USERMENU_BOTTOM_RIGHT)) {
			CielDebugger.log("Showing Thumbnailmenu @ BOTTOM_RIGHT");
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_BOTTOM_RIGHT)).setVisible(true);
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_BOTTOM_RIGHT)).sendToFront();
		} else if(Button.equals(CielSettings.Childs.USERMENU_TOP_LEFT)) {
			CielDebugger.log("Showing Thumbnailmenu @ TOP_LEFT");
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_TOP_LEFT)).setVisible(true);
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_TOP_LEFT)).sendToFront();
		} else if(Button.equals(CielSettings.Childs.USERMENU_TOP_RIGHT)) {
			CielDebugger.log("Showing Thumbnailmenu @ TOP_RIGHT");
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_TOP_RIGHT)).setVisible(true);
			((CielThumbnailMenu) getCanvas().getChildByName(CielSettings.Childs.THUMBNAILMENU_TOP_RIGHT)).sendToFront();
		}
	}
	
	/**
	 * Erzeugt eine verkleinerte Version der übergebenen Bilddatei uns speichert diese in einem, in @see CielSettings definiertem
	 * Pfad.
	 * @param f Datei des ursprünglichen Bildes
	 */
	private void createThumbnail(File f) {
		try {
            Image img = new ImageIcon(ImageIO.read(f)).getImage();
            int wNew = img.getWidth(null)/5, hNew = img.getHeight(null)/5;
            Image scaledImage = img.getScaledInstance(wNew, hNew, Image.SCALE_SMOOTH);
            BufferedImage outImg = new BufferedImage(wNew, hNew,BufferedImage.TYPE_INT_RGB);
            Graphics g = outImg.getGraphics();
            g.drawImage(scaledImage, 0, 0, null);
            g.dispose();
            ImageIO.write(outImg, "jpeg", new File(CielSettings.Filesystem.THUMBDIR+"//"+f.getName()));
        } catch (IOException e) { e.printStackTrace(); }
    }
	
	
	/**
	 * Hintegrundthread der den Bilderordner auf neu hinzugefügte Bilder überwacht.
	 * @author Alexander Bazo
	 *
	 */
	private class FileObserver extends Thread {
		
		/**
		 * Anzahl der Bilder im überwachten Ordner und der Pfad zum überwachten Ordner
		 */
		int fileCount;
		File storage;
		
		/**
		 * Konstruktor des Threads
		 * @param path Pfad zum zu überwachenden Ordner
		 */
		public FileObserver(String path) {
			storage = new File(path);
			fileCount = countFiles();
			CielDebugger.log("Observer created. Currently there are "+fileCount+" pictures.");
		}

		@Override
		public void run() {
			/**
			 * Alle 2 Sekunden wird überprüft ob die gespeicherte Anzahl an Bildern mit der aktuellen Anzahl im Ordner übereinstimmt.
			 * Wenn nicht wird versucht die neue Datei zu importieren.
			 */
			while(true) {
				try { sleep(2000); } catch (InterruptedException e) {}
				if(fileCount < countFiles()) {
					File f = getNewImageName();
					if(f != null) {
						CielDebugger.log("New JPG detected: "+f.getName());;
						importFile(f, CielSettings.Scans.RUNTIME_SCAN);
					}
				
				}
			}
		}
		
		/**
		 * Überprüft alle jpegs im Ordner hinsichtlich dessen, ob sie schon in der HashMap der Scene gespeichert sind.
		 * @return Das Bild zurück, das im überwachten Ordner liegt und noch nicht in der HashMap der Scene gespeichert ist.
		 */
		private File getNewImageName() {
			File[] photoArray = storage.listFiles();
			/**
			 * Für jede Datei im Ordner ...
			 */
			for(File f: photoArray) {
				/**
				 * ... wird überprüft ob sie eine jpeg-Dateiendung hat ...
				 */
				if(f.getName().contains(".jpg") || f.getName().contains(".jpeg") || f.getName().contains(".JPG") || f.getName().contains("JPEG")) {
					/**
					 * ... und ob sie schon in der HashMap der Scene gespeichert ist.
					 */
					if(!images.containsKey(f.getName())) {
						return f;
					}
				}
			}
			photoArray = null;
			System.gc();
			return null;
		}

		/**
		 * Zählt alle Dateien im überwachten Ordner die eine jpeg-Dateiendung haben
		 * @return Anzahl der Dateien im Ordner mit einer Endung wie jpg/jpeg/JPG/JPEG
		 */
		private int countFiles() {
			int count = 0;
			File[] photoArray = storage.listFiles();
			for(File f: photoArray) {
				if(f.getName().contains(".jpg") || f.getName().contains(".jpeg") || f.getName().contains(".JPG") || f.getName().contains("JPEG")) {
					count++;
				}
			}
			photoArray = null;
			return count;
		}
		
	}
}


